package pl.edu.mimuw.irs.rcp.controller.editor.requirements;

import org.eclipse.gef.EditDomain;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.dnd.TransferData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;

import pl.edu.mimuw.irs.core.model.design.resource.IHolder;
import pl.edu.mimuw.irs.core.model.design.resource.IMember;
import pl.edu.mimuw.irs.core.model.design.resource.Requirement;
import pl.edu.mimuw.irs.core.model.design.resource.Requirements;
import pl.edu.mimuw.irs.core.model.design.resource.Role;
import pl.edu.mimuw.irs.core.model.design.resource.RoleInstance;
import pl.edu.mimuw.irs.rcp.controller.editor.resources.AdaptableResourceWrapper;
import pl.edu.mimuw.irs.rcp.controller.editor.roles.AdaptableRoleInstanceWrapper;
import pl.edu.mimuw.irs.rcp.controller.editor.util.IAdaptableDataWrapper;
import pl.edu.mimuw.irs.rcp.controller.editor.util.command.AddMemberCommand;
import pl.edu.mimuw.irs.rcp.controller.editor.util.command.DeleteMemberCommand;
import pl.edu.mimuw.irs.rcp.util.dnd.RequirementsTransfer;
import pl.edu.mimuw.irs.rcp.util.dnd.RolesInstancesTransfer;
import pl.edu.mimuw.irs.rcp.util.dnd.RolesTransfer;

public class RequirementsTreeWrapper {

	// *** Attributes

	private Requirements requirements;
	private EditDomain editDomain;
	private TreeViewer treeViewer;
	public Requirements getRequirements() {
		if (requirements == null) requirements = new Requirements();
		return requirements;
	}
	public EditDomain getEditDomain() {
		return editDomain;
	}
	public TreeViewer getTreeViewer() {
		return treeViewer;
	}
	public CommandStack getCommandStack() {
		return getEditDomain().getCommandStack();
	}
	
	// *** Constructors

	public RequirementsTreeWrapper(Composite parent, int style, 
			Requirements requirements, EditDomain editDomain) {
		treeViewer = new TreeViewer(parent, style);
		this.requirements = requirements;
		this.editDomain = editDomain;
		
		redrawTree();
		
		// Drag'N'Drop
		addDragSource();
		addDropTarget();
	}
	
	// *** Methods
	
	public void redrawTree() {
		treeViewer.getTree().removeAll();
		for (Requirement requirement : getRequirements()) {
			TreeItem parent = new TreeItem(treeViewer.getTree(), SWT.BORDER);
			parent.setText(new String[] {requirement.getName()});
			parent.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(
				ISharedImages.IMG_OBJ_ELEMENT));
			// Store resource in tree item
			Object requirementWrapper = new AdaptableRequirementWrapper(requirement, editDomain); 
			parent.setData(requirementWrapper);
			for (RoleInstance role : requirement.getRolesMap().values()) {
				TreeItem child = new TreeItem(parent, SWT.BORDER);
				child.setText(new String[] {role.getRole().getName()});
				child.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(
					ISharedImages.IMG_OBJ_ELEMENT));
				// Store requirement in tree item
				Object roleWrapper = new AdaptableRoleInstanceWrapper(role, editDomain); 
				child.setData(roleWrapper);
			}
		}

		treeViewer.getTree().redraw();
	}
	
	private void addDragSource() {
		final Tree tree = treeViewer.getTree();
		int operations = DND.DROP_MOVE | DND.DROP_COPY;
		final DragSource source = new DragSource(tree, operations);
		
		Transfer[] types = new Transfer[] { RequirementsTransfer.getInstance(), RolesInstancesTransfer.getInstance() };
	 	source.setTransfer(types);
		
	 	source.addDragListener(new DragSourceListener() {
	 		private TransferData lastDataType; 
			private IMember[] lastObjects; 
	 		@SuppressWarnings("unchecked")
			public void dragStart(DragSourceEvent event) {
	 			if (tree.getSelection().length == 0) {
	 				event.doit = false;
	 			} else {
	 				Object[] selection = tree.getSelection();
	 				boolean rolesInstances = true;
	 				boolean requirements = true;
	 				/*
	 				 * Make sure all are of one type that we can handle.
	 				 */
	 				for (Object object : selection) {
	 					TreeItem item = (TreeItem) object;
	 					IAdaptableDataWrapper wrapper = (IAdaptableDataWrapper) item.getData();
	 					if (!(wrapper.getData() instanceof RoleInstance)) rolesInstances = false;
	 					if (!(wrapper.getData() instanceof Requirement)) requirements = false;
	 					if (!rolesInstances && !requirements) {
	 						event.doit = false;
	 						break;
	 					}
	 				}
	 				
	 				if (rolesInstances) source.setTransfer(new Transfer[] { RolesInstancesTransfer.getInstance() });
	 				else source.setTransfer(new Transfer[] { RequirementsTransfer.getInstance() });
	 			}
	 		}
	 		public void dragSetData(DragSourceEvent event) {
	 			// Provide the data of the requested type.
	 			lastDataType = event.dataType;
	 			if (RolesInstancesTransfer.getInstance().isSupportedType(lastDataType)) {
	 				RoleInstance[] roles = new RoleInstance[tree.getSelection().length];
	 				Object[] selection = tree.getSelection();
	 				for (int i = 0; i < selection.length; i++) {
	 					TreeItem item = (TreeItem)selection[i];
	 					AdaptableRoleInstanceWrapper wrapper = 
	 						(AdaptableRoleInstanceWrapper) item.getData();
	 					roles[i] = wrapper.getData();
	 				}
	 				lastObjects = roles;
	 				event.data = roles;
	 			} else if (RequirementsTransfer.getInstance().isSupportedType(lastDataType)) {
	 				Requirement[] requirements = new Requirement[tree.getSelection().length];
	 				Object[] selection = tree.getSelection();
	 				for (int i = 0; i < selection.length; i++) {
	 					TreeItem item = (TreeItem)selection[i];
	 					AdaptableRequirementWrapper wrapper = 
	 						(AdaptableRequirementWrapper) item.getData();
	 					requirements[i] = wrapper.getData();
	 				}
	 				lastObjects = requirements;
	 				event.data = requirements;
	 			}
	 		}
	 		public void dragFinished(DragSourceEvent event) {
	 			if (event.doit == false) return;
	 			if ((event.detail & DND.DROP_MOVE) == 0) return;
	 			if (RolesInstancesTransfer.getInstance().isSupportedType(lastDataType) ||
	 					RequirementsTransfer.getInstance().isSupportedType(lastDataType)) {
	 				for (int i = 0; i < lastObjects.length; i++) {
	 					delete(lastObjects[i]);
	 				}
	 			}
			}
		});
	}
	
	private void addDropTarget() {
		Tree tree = treeViewer.getTree();
		DropTarget target = new DropTarget(tree, DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK | DND.DROP_DEFAULT);
		Transfer[] types = new Transfer[] { RolesTransfer.getInstance(), RolesInstancesTransfer.getInstance(), RequirementsTransfer.getInstance() };
		target.setTransfer(types);
		target.addDropListener(new DropTargetAdapter() {
			
			public void dragEnter(DropTargetEvent event) {
				if (event.detail == DND.DROP_DEFAULT) {
					event.detail = (event.operations & DND.DROP_LINK) != 0 ? DND.DROP_LINK
							: (event.operations & DND.DROP_MOVE) != 0 ? DND.DROP_MOVE
									: (event.operations & DND.DROP_COPY) != 0 ? DND.DROP_COPY
											: DND.DROP_NONE;
				}
				for (int i = 0, n = event.dataTypes.length; i < n; i++) {
					if (event.currentDataType == null) {
						if (RolesTransfer.getInstance().isSupportedType(
								event.dataTypes[i])) {
							event.currentDataType = event.dataTypes[i];
						}
					}
					if (RequirementsTransfer.getInstance().isSupportedType(
							event.dataTypes[i])) {
						event.currentDataType = event.dataTypes[i];
					}
				}
			}

			public void dragOver(DropTargetEvent event) {
				event.feedback = DND.FEEDBACK_SELECT | DND.FEEDBACK_SCROLL;
			}

			public void drop(DropTargetEvent event) {
				if (RolesTransfer.getInstance().isSupportedType(
						event.currentDataType)) {
					Object[] roles = (Object[]) event.data;
					if (roles == null) return;
					TreeItem targetItem = (TreeItem) event.item;
					Requirement requirement;
					
					if (targetItem != null && (targetItem.getData()
							instanceof AdaptableRequirementWrapper)) {
						AdaptableRequirementWrapper wrapper = 
							(AdaptableRequirementWrapper)targetItem.getData();
						requirement = wrapper.getData();
					} else {
						requirement = createRequirement(getRequirements());
					}
					for (Object role : roles) addRole(requirement, (Role)role);
				} else if (RequirementsTransfer.getInstance().isSupportedType(
						event.currentDataType)) {
					Object[] requirements = (Object[]) event.data;
					if (requirements == null) return;
					
					for (Object object : requirements) {
						copy(getRequirements(), (Requirement)object);
					}
				} else if (RolesInstancesTransfer.getInstance().isSupportedType(
						event.currentDataType)) {
					Object[] roles = (Object[]) event.data;
					if (roles == null) return;
					TreeItem targetItem = (TreeItem) event.item;
					Requirement requirement;
					
					if (targetItem != null && (targetItem.getData()
							instanceof AdaptableResourceWrapper)) {
						AdaptableRequirementWrapper wrapper = 
							(AdaptableRequirementWrapper)targetItem.getData();
						requirement = wrapper.getData();
					} else {
						requirement = createRequirement(getRequirements());
					}
					for (Object object : roles) {
						copy(requirement, (RoleInstance) object);
					}
				}
			}
		});
	}


	protected Requirement createRequirement(Requirements requirements) {
		Requirement requirement = new Requirement();
		AddMemberCommand command = new AddMemberCommand(requirements, requirement);
		getCommandStack().execute(command);
		return requirement;
	}
	
	private void addRole(Requirement requirement, Role role) {
		AddMemberCommand command = new AddMemberCommand(requirement, new RoleInstance(role));
		getCommandStack().execute(command);
	}

	@SuppressWarnings("unchecked")
	private void copy(IHolder holder, IMember member) {
		AddMemberCommand command = new AddMemberCommand(holder, member.copy());
		getCommandStack().execute(command);
	}
	
	private void delete(IMember member) {
		DeleteMemberCommand command = new DeleteMemberCommand(member);
		getCommandStack().execute(command);
	}

	public void setLayoutData(Object layoutData) {
		treeViewer.getTree().setLayoutData(layoutData);
	}

}
